/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package sd_proj1.monSolution;

import java.util.TreeMap;
import sd_proj1.interfaces.*;

/**
 * Esta classe StoreMonitor cria um objecto do tipo Loja onde vão interagir a dona e os clientes.
 * Neste monitor numa versão prévia foi incluído um array de estados do cliente, que depois podia ter sido retirada
 * mas optou-se por continuar com este array para nos podermos guiar ao longo do programa.
 *
 * @author Pedro Costa Correia - 32795
 * @author Valter Pedro Lopes Vicente - 39360
 */
public class StoreMonitor implements ICustomer, IOwnerStore {

	/**
	 *  Este tipo de dados implementa a Loja.
	 *  Solução concorrente baseada em monitores como elemento de sincronização entre os threads Customer e Owner.
	 */
	/**
	 *  Constantes que caracterizam o estado interno dos threads cliente
	 */
	public enum customerState {

		/**
		 *  estado independente na simulação, o cliente deve ser posto a dormir durante um intervalo de tempo aleatório
		 */
		CARRYING_OUT_DAILY_CHORES,
		/**
		 * estado de transição, verificação se a porta da loja está aberta
		 */
		CHECKING_DOOR_OPEN,
		/**
		 * estado de transição, verificação das prateleiras da loja por produtos
		 */
		APPRAISING_OFFER_IN_DISPLAY,
		/**
		 * estado de bloqueio o cliente é acordado pela dona da empresa quando completa a transacção, sayGoodByeToCustomer
		 */
		BUYING_SOME_GOODS
	}
	/**
	 * Parâmetro booleano que define o estado da loja.
	 * false - Fechada
	 * true - Aberta
	 */
	private boolean storeState = true;
	/**
	 *  Instanciação da interface do artesão implementada no estado geral.
	 *
	 *    @serialField ownerState
	 */
	private IOwnerFifo ownerState;
	/**
	 *  Vector de armazenamento da quantidade de produtos comprados pelos clientes.
	 *
	 *    @serialField customerProducts
	 */
	private int customerProducts[];
	/**
	 *  Número de clientes que frequentam a loja
	 *
	 *    @serialField nCustomer
	 */
	private int nCustomer = 0;
	/**
	 * Número de clientes dentro da loja
	 *
	 * @serialField nCustInStore
	 */
	private int nCustInStore = 0;
	/**
	 *  Estado presente dos clientes
	 *
	 *    @serialField customerState
	 */
	private customerState[] customersState;
	/**
	 * Número de peças na loja
	 *
	 * @serialField nProducts
	 */
	private int nProducts = 0;
	/**
	 *  Parâmetro booleano que define se a dona está morta ou não.
	 *
	 * @serialField donaIsDead
	 */
	private boolean donaIsDead = false;
	/**
	 *  Usado para armazenamento de mensagens.
	 *
	 *    @serialField map
	 */
	private TreeMap map;
	/**
	 *  Uma mensagem para ser usada no log.
	 *
	 *    @serialField mlog
	 */
	private Message mlog;

	/**
	 *  Instanciação da loja.
	 *
	 *    @param nCustomer Número de clientes que frequentam a loja.
	 *    @param nProducts Número de produtos no ínicio da loja.
	 *    @param ownerState Estado geral onde se encontra o fifo de pedidos e o estado da dona da loja.
	 *    @param map Mapa onde vão ser guardados os registos de log.
	 */
	public StoreMonitor(int nCustomer, int nProducts, IOwnerFifo ownerState, TreeMap map) {

		this.nProducts = nProducts;
		this.ownerState = ownerState;
		this.nCustomer = nCustomer;
		this.map = map;

		customerProducts = new int[nCustomer];
		customersState = new customerState[this.nCustomer];

		for (int i = 0; i < this.nCustomer; i++) {
			customersState[i] = customerState.CARRYING_OUT_DAILY_CHORES;
			customerProducts[i] = 0;
		}

		mlog = new Message();
		mlog.setShopStat(nCustInStore, nProducts, 'F', 'F');
		map.put(Clock.getInstance().getValue(), mlog);
	}

	/**
	 * Método de finalização da thread cliente.
	 * @param customerId.
	 * @return <li> true - quando o cliente pode morrer, ou seja, quando a dona está morta.
	 *		<li> false - caso o cliente não tenha de morrer.
	 *
	 */
	public synchronized boolean endOperCustomer(int customerId) {
		if (isDonaIsDead()) /**Informação que a dona não vai recarregar mais peças*/
		{
			return (true);
		} else {
			if (nCustInStore == 0 && nProducts == 0 && ownerState.isAllCraftsDead() && !isDonaIsDead()) {
				ownerState.emptyRequest();
			}
		}
		return (false);
	}

	/**
	 *Método de finalização da thread dona da loja
	 * @return<li> true - quando a dona está em fase que pode morrer, ou seja quando os artesãos estao todos mortos,
	 *			não existem clientes na loja  e que não existem mais produtos para os mesmos comprarem.
	 *		<li> false - caso contrário.
	 */
	public synchronized boolean endOperEntrep() {
		if (ownerState.isAllCraftsDead() && nCustInStore == 0 && nProducts == 0) {
			donaIsDead = true;

			return (true);
		} else {
			return (false);
		}
	}

	/**
	 * Monitor invocado pela dona da loja indicando que a loja foi fechada com sucesso.
	 * 
	 */
	public synchronized void closeTheDoor() {
		mlog = new Message();
		this.storeState = false;
		mlog.setShopDoorStat(Message.storeStat.SDCL);
		map.put(Clock.getInstance().getValue(), mlog);
	}

	/**
	 * Monitor invocado pela dona definindo a abertura da loja.
	 *
	 */
	public synchronized void prepareToWork() {
		mlog = new Message();
		if (this.nProducts != 0) {
			this.storeState = true;
			mlog.setOwnerStat(Message.ownerStat.WFNT);
			mlog.setShopDoorStat(Message.storeStat.SPOP);
		} else {
			mlog.setOwnerStat(Message.ownerStat.WFNT);
		}
		map.put(Clock.getInstance().getValue(), mlog);
	}

	/**
	 * Monitor invocado pela dona, alterando o estado do cliente e da dona, depois desta o ter atendido.
	 *
	 * @param customerId Identificação do cliente.
	 */
	public synchronized void sayGoodByeToCustomer(int customerId) {
		mlog = new Message();
		customersState[customerId] = customerState.CARRYING_OUT_DAILY_CHORES;	// sinaliza que o cliente vai prosseguir com a sua vida
		notifyAll();
		mlog.setOwnerStat(Message.ownerStat.ATAC);
		mlog.setstatCustomer(Message.customerStat.CODC, customerProducts[customerId], customerId);
		if (this.nProducts == 0) {
			closeTheDoor();
		}
		map.put(Clock.getInstance().getValue(), mlog);
	}

	/**
	 * Monitor invocado pela dona para verificação da existencia de clientes na loja
	 * @return <li> true, se existirem.
	 *            <li> false, em caso contrário.
	 */
	public synchronized boolean customersInTheShop() {
		if (nCustInStore != 0) {
			return (true);
		} else {
			return (false);
		}
	}

	/**
	 *   Monitor invocado pela dona, sinalizando que fechou a loja e vai em direcção à oficina.
	 */
	public void prepareToLeave() {
		mlog = new Message();
		mlog.setOwnerStat(Message.ownerStat.CLTS);
		mlog.setShopStat(nCustInStore, nProducts, '0', '0');
		map.put(Clock.getInstance().getValue(), mlog);
	}

	/**
	 * Monitor invocado pela dona, informando que regressou à loja.
	 */
	public synchronized void returnToShop() {
		mlog = new Message();
		int i;
		i = ownerState.getNProductsTransport();

		if (i != 0) {
			nProducts = nProducts + i;
			ownerState.setNProductsTransport(0);
		}
		mlog.setOwnerStat(Message.ownerStat.OPTS);
		mlog.setShopStat(nCustInStore, nProducts, '0', '0');

		map.put(Clock.getInstance().getValue(), mlog);
	}

	/**
	 *  O cliente verifica os produtos expostos na loja e escolhe um (operação interna).
	 *
	 * @param customerId Identificação do cliente.
	 * @return Quantidade de produtos escolhidos pelo cliente.
	 */
	public synchronized int perusingAround(int customerId) {
		mlog = new Message();
		int result = randint(0, nProducts);
		customerProducts[customerId] = customerProducts[customerId] + result;
		this.nProducts = nProducts - result;
		mlog.setShopStat(nCustInStore, nProducts, '0', '0');

		map.put(Clock.getInstance().getValue(), mlog);
		return result;
	}

	/**
	 * Monitor invocado pelo cliente, com a pretensão de ir visitar a loja.
	 *
	 * @param customerId Identificação do cliente.
	 */
	public synchronized void goShopping(int customerId) {
		mlog = new Message();
		customersState[customerId] = customerState.CHECKING_DOOR_OPEN;
		mlog.setstatCustomer(Message.customerStat.CSDO, 0, customerId);
		map.put(Clock.getInstance().getValue(), mlog);
	}

	/**
	 * Monitor invocado pelo cliente, entrando este na loja se esta estiver aberta ou voltando às suas tarefas caso contrário.
	 * @param customerId Identificação do cliente.
	 */
	public synchronized void enterShop(int customerId) {
		mlog = new Message();
		if (isDoorOpen()) {
			nCustInStore += 1;
			customersState[customerId] = customerState.APPRAISING_OFFER_IN_DISPLAY;
			mlog.setstatCustomer(Message.customerStat.AOID, customerProducts[customerId], customerId);
			mlog.setShopStat(nCustInStore, nProducts, '0', '0');

		} else {
			customersState[customerId] = customerState.CARRYING_OUT_DAILY_CHORES;
			mlog.setstatCustomer(Message.customerStat.CODC, customerProducts[customerId], customerId);
			mlog.setShopStat(nCustInStore, nProducts, '0', '0');
		}
		map.put(Clock.getInstance().getValue(), mlog);
	}

	/**
	 * Monitor invocado pelo cliente, qunado este sai da loja e volta à vida normal.
	 *
	 * @param customerId Identificação do cliente.
	 */
	public synchronized void exitShop(int customerId) {
		mlog = new Message();
		nCustInStore -= 1;
		mlog.setstatCustomer(Message.customerStat.CODC, customerProducts[customerId], customerId);
		mlog.setShopStat(nCustInStore, nProducts, '0', '0');
		map.put(Clock.getInstance().getValue(), mlog);
	}

	/**
	 *  Operação de ir comprar algum produto (originada pelo cliente).
	 *
	 * @param customerId Identificação do cliente.
	 * @param ng Número de produtos que o cliente pretende.
	 *
	 * @return <li> true, se conseguiu comprar o que desejava.
	 *            <li> false, em caso contrário.
	 */
	public synchronized boolean iWantThis(int customerId, int ng) {
		mlog = new Message();
		customersState[customerId] = customerState.BUYING_SOME_GOODS;

		// aguarda a continuação das operações
		while (customersState[customerId] != customerState.CARRYING_OUT_DAILY_CHORES) {
			mlog.setstatCustomer(Message.customerStat.BYSG, customerProducts[customerId], customerId);
			try {
				wait();
			} catch (InterruptedException e) {
			}
		}

		map.put(Clock.getInstance().getValue(), mlog);
		return (true);		// abandona a loja com a compra efectuada
	}

	/**
	 *  Verificação por parte dos clientes se a loja se encontra aberta
	 *
	 * @return<li> false - se a porta está fechada
	 *		 <li> true -  se a porta está aberta
	 */
	public synchronized boolean isDoorOpen() {
		if (storeState == true) {
			return (true);
		} else {
			return (false);
		}
	}

	/**
	 * Verificação do estado de vida da dona por parte dos clientes.
	 * @return <li> true, se estiver morta.
	 *            <li> false, se estiver viva..
	 */
	public boolean isDonaIsDead() {
		return donaIsDead;
	}

	/**
	 * Função auxiliar para cálculo de um valor aleatório entre um intervalo definido por low e high.
	 * @param low Valor mais baixo do intervalo (inclusive).
	 * @param high Valor mais alto do intervalo (exclusive).
	 * @return O valor obtido.
	 */
	private int randint(int low, int high) {
		return ((int) (((high - low) * (Math.random()) + 0.5)) + low);
	}
}
